﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Assists the user during the insertion of a new <see cref="CoreSymbol"/>.
    /// </summary>
    public class CoreSymbolInserter : INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="CoreSymbolInserter"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="MusicSectionInterior"/>
        /// </para>
        /// <para>
        /// The music section interior that owns this inserter.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        public CoreSymbolInserter(MusicSectionInterior owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            this.owner = owner;
            ledgerPool = new List<InserterLedgerLine>();
            ledgerLines = new ObservableCollection<InserterLedgerLine>();
            systemRects = new List<Rect>();

            for (int i = 0; i < 10; ++i)
                ledgerPool.Add(new InserterLedgerLine(this));

            isVisible = false;
            IsSymbolVisible = false;
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <inheritdoc/>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets the default voice for a given staff.
        /// </summary>
        /// <param name="staff">
        /// <para>The staff for which the voice is being determined.</para>
        /// </param>
        /// <returns>The number of the default voice for the staff</returns>
        private byte GetDefaultVoice(Staff staff)
        {
            var sd = ((Model.Elements.CWNotation.Staff)staff.AdaptedElement).Descriptor;
            var index = sd.FindOwner<Model.Elements.CWNotation.CWPartData>().Staves.IndexOf(sd);

            return (byte)(index * 3 + 1);
        }

        /// <summary>
        /// Moves a given count of ledger lines from the pool to the end of the <see cref="LedgerLines"/> collection.
        /// </summary>
        /// <param name="count">The requested number of ledger lines</param>
        private void InsertLedgerLines(int count)
        {
            if (ledgerPool.Count < count)
            {
                for (int i = ledgerPool.Count; i < count; ++i)
                    ledgerPool.Add(new InserterLedgerLine(this));
            }

            for (int i = 1; i <= count; ++i)
                ledgerLines.Add(ledgerPool[ledgerPool.Count - i]);

            ledgerPool.RemoveRange(ledgerPool.Count - count, count);
        }

        /// <summary>
        /// Initializes auxiliary structures.
        /// </summary>
        private void Init()
        {
            systemRects.Clear();

            for (int i = 0; i < owner.Systems.Count; ++i)
            {
                var left = owner.Systems[i].Left;
                var top = owner.Systems[i].Top;
                var right = owner.Systems[i].Width + left;
                var bottom = owner.Systems[i].Height + top;

                if (i == 0)
                    top = 0;
                if (i == owner.Systems.Count - 1)
                    bottom = owner.FindOwner<MusicSection>().Height;
                else
                {
                    if (i > 0)
                    {
                        var prevSystem = owner.Systems[i - 1];
                        top -= (owner.Systems[i].Top - prevSystem.Top - prevSystem.Height) / 2;
                    }
                    if (i < owner.Systems.Count - 1)
                    {
                        var nextSystem = owner.Systems[i + 1];
                        bottom += (nextSystem.Top - owner.Systems[i].Top - owner.Systems[i].Height) / 2;
                    }
                }

                systemRects.Add(new Rect(left, top, right - left, bottom - top));
            }
        }

        /// <summary>
        /// Moves a given count of ledger lines from the end of the <see cref="LedgerLines"/> collection back to the pool.
        /// </summary>
        /// <param name="count">The requested number of ledger lines</param>
        private void RemoveLedgerLines(int count)
        {
            for (int i = 1; i <= count; ++i)
                ledgerPool.Add(ledgerLines[ledgerLines.Count - i]);

            var stop = ledgerLines.Count - count;

            for (int i = ledgerLines.Count - 1; i >= stop; --i)
                ledgerLines.RemoveAt(i);
        }

        /// <summary>
        /// Sets the content of the ledger lines collection.
        /// </summary>
        /// <param name="staffPosition">The actual staff position of the inserter symbol</param>
        private void SetLedgerLines(int staffPosition)
        {
            var countAbove = Math.Max(0, (staffPosition - ActiveStaff.Lines.Count * 2 + 2) / 2);
            var countBellow = Math.Max(0, (-staffPosition) / 2);
            var count = Math.Max(countAbove, countBellow);

            if (SymbolType != InsertedSymbolType.Note)
                count = 0;

            if (count > ledgerLines.Count)
                InsertLedgerLines(count - ledgerLines.Count);
            else if (count < ledgerLines.Count)
                RemoveLedgerLines(ledgerLines.Count - count);

            if (staffPosition < 0)
            {
                for (int pos = -2, i = 0; i < ledgerLines.Count; pos -= 2, ++i)
                    ledgerLines[i].StaffPosition = pos;
            }
            else
            {
                for (int pos = ActiveStaff.Lines.Count * 2, i = 0; i < ledgerLines.Count; pos += 2, ++i)
                    ledgerLines[i].StaffPosition = pos;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Hides the inserter.
        /// </summary>
        public void HideInserter()
        {
            IsSymbolVisible = false;
            isVisible = false;
            systemRects.Clear();
        }
        
        /// <summary>
        /// Inserts the symbol.
        /// </summary>
        public void InsertSymbol()
        {
            if (!(isVisible && IsSymbolVisible))
                return;

            if (SymbolType == InsertedSymbolType.BassClef || SymbolType == InsertedSymbolType.TenoreClef || SymbolType == InsertedSymbolType.TrebleClef)
            {
                ushort beat = 0;
                var modelStaff = ActiveStaff.AdaptedElement as Model.Elements.CWNotation.Staff;
                var lengthConverter = new Converters.LengthConverter();
                var symbol = modelStaff.Symbols.LastOrDefault(s => s.Measure == activeMeasure && (double)lengthConverter.Convert(s.Position, typeof(double), null, null) <= position);
                if (symbol != null)
                    beat = (ushort)(symbol.Beat.Beginning + (symbol is Model.Elements.CWNotation.DurationalSymbol ? ((Model.Elements.CWNotation.DurationalSymbol)symbol).PresentedElement.Duration : 0));
                ClefType clefType;
                int staffPosition;
                if (SymbolType == InsertedSymbolType.BassClef)
                {
                    clefType = ClefType.F;
                    staffPosition = 6;
                }
                else if (SymbolType == InsertedSymbolType.TenoreClef)
                {
                    clefType = ClefType.C;
                    staffPosition = 6;
                }
                else
                {
                    clefType = ClefType.G;
                    staffPosition = 2;
                }

                var cmd = new InsertClefCommand(ActiveStaff.UndoStack, ActiveStaff.RedoStack)
                {
                    Beat = beat,
                    ClefType = clefType,
                    Measure = activeMeasure,
                    Staff = ActiveStaff,
                    StaffPosition = staffPosition
                };
                cmd.Execute(null);

                HideInserter();
                return;
            }

            var actualVoice = (voice > 0 ? voice : GetDefaultVoice(ActiveStaff));
            var activeSymbols = from symbol in ActiveStaff.Symbols
                                where ((Model.Elements.CWNotation.CoreSymbol)symbol.AdaptedElement).Measure == activeMeasure &&
                                      symbol is DurationalSymbol && ((DurationalSymbol)symbol).Voice == actualVoice &&
                                      symbol.Position <= position + ActiveStaff.SpaceSize
                                select symbol as DurationalSymbol;

            DurationalSymbol activeSymbol = null;
            if (activeSymbols.Count() > 0)
                activeSymbol = activeSymbols.Last();

            UndoableCommandBase insertCommand = null;

            if (activeSymbol != null)
            {
                var env = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).GetEnvironmentFor(activeSymbol.AdaptedElement as Model.Elements.CWNotation.DurationalSymbol);
                var tone = env.GetTone(activeStaffPosition);
                if (Accidental != AccidentalType.None)
                    tone.Accidental = Model.Elements.CWNotation.Accidental.AccidentalTypeToValue(
                        (Model.Elements.CWNotation.AccidentalType)Enum.Parse(typeof(Model.Elements.CWNotation.AccidentalType), Accidental.ToString())
                        );

                if (activeSymbol.Position >= position)
                {
                    if (SymbolType == InsertedSymbolType.Rest)
                        throw new InvalidOperationException("Cannot insert symbol!");
                    else if (activeSymbol is Chord)
                    {
                        insertCommand = new InsertNoteCommand(ActiveStaff.UndoStack, ActiveStaff.RedoStack)
                        {
                            AugmentationDots = (byte)activeSymbol.AugmentationDots.Count,
                            BasicDuration = activeSymbol.BasicDuration,
                            Beat = ((Model.Elements.CWNotation.DurationalSymbol)activeSymbol.AdaptedElement).Beat.Beginning,
                            BeatConflictAction = BeatConflictActionType.AddToChord,
                            Measure = activeMeasure,
                            Project = ActiveStaff.FindOwner<Elements.Project>(),
                            StaffDescriptor = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).Descriptor,
                            Tone = tone,
                            Voice = actualVoice
                        };
                    }
                    else
                        throw new InvalidOperationException("Cannot insert symbol!");
                }
                else
                {
                    var durSymElement = ((Model.Elements.CWNotation.DurationalSymbol)activeSymbol.AdaptedElement).PresentedElement;
                    var beat = (ushort)(durSymElement.FindOwner<Model.Elements.BeatElement>().Beginning + durSymElement.Duration);
                    if (durSymElement.FindOwner<Model.Elements.MeasureElement>() != activeMeasure)
                        beat = 0;

                    if (SymbolType == InsertedSymbolType.Rest)
                    {
                        insertCommand = new InsertRestCommand(ActiveStaff.UndoStack, ActiveStaff.RedoStack)
                        {
                            AugmentationDots = AugmentationDots,
                            BasicDuration = BasicDuration,
                            Beat = beat,
                            BeatConflictAction = BeatConflictActionType.Move,
                            Measure = activeMeasure,
                            Project = ActiveStaff.FindOwner<Project>(),
                            StaffDescriptor = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).Descriptor,
                            Voice = actualVoice
                        };
                    }
                    else
                    {
                        insertCommand = new InsertNoteCommand(ActiveStaff.UndoStack, ActiveStaff.RedoStack)
                        {
                            AugmentationDots = AugmentationDots,
                            BasicDuration = BasicDuration,
                            Beat = beat,
                            BeatConflictAction = BeatConflictActionType.Move,
                            Measure = activeMeasure,
                            Project = ActiveStaff.FindOwner<Elements.Project>(),
                            StaffDescriptor = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).Descriptor,
                            Tone = tone,
                            Voice = actualVoice
                        };
                    }
                }
            }
            else
            {
                var env = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).GetMeasureEnvironment(activeMeasure);
                var tone = env.GetTone(activeStaffPosition);
                if (Accidental != AccidentalType.None)
                    tone.Accidental = Model.Elements.CWNotation.Accidental.AccidentalTypeToValue(
                        (Model.Elements.CWNotation.AccidentalType)Enum.Parse(typeof(Model.Elements.CWNotation.AccidentalType), Accidental.ToString())
                        );

                if (SymbolType == InsertedSymbolType.Rest)
                {
                    insertCommand = new InsertRestCommand(ActiveStaff.UndoStack, ActiveStaff.RedoStack)
                    {
                        AugmentationDots = AugmentationDots,
                        BasicDuration = BasicDuration,
                        Beat = 0,
                        BeatConflictAction = BeatConflictActionType.Move,
                        Measure = activeMeasure,
                        Project = ActiveStaff.FindOwner<Project>(),
                        StaffDescriptor = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).Descriptor,
                        Voice = actualVoice
                    };
                }
                else
                {
                    insertCommand = new InsertNoteCommand(ActiveStaff.UndoStack, ActiveStaff.RedoStack)
                    {
                        AugmentationDots = AugmentationDots,
                        BasicDuration = BasicDuration,
                        Beat = 0,
                        BeatConflictAction = BeatConflictActionType.Move,
                        Measure = activeMeasure,
                        Project = ActiveStaff.FindOwner<Elements.Project>(),
                        StaffDescriptor = ((Model.Elements.CWNotation.Staff)ActiveStaff.AdaptedElement).Descriptor,
                        Tone = tone,
                        Voice = actualVoice
                    };
                }
            }

            insertCommand.Execute(null);           
        }

        /// <summary>
        /// Sets the current position of the inserter
        /// (typically it is the position of the mouse cursor).
        /// </summary>
        /// <param name="position">
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The position of the inserter relative to the top-left corner of the owner section.
        /// </para>
        /// </param>
        public void SetPosition(Point position)
        {
            if (!isVisible)
                return;

            System system = null;

            if (owner.FindOwner<Page>() != null)
            {
                for (int i = 0; i < owner.Systems.Count; ++i)
                {
                    if (systemRects[i].Contains(position))
                    {
                        system = owner.Systems[i];
                        break;
                    }
                }
            }
            else
            {
                system = (owner.Systems.Count > 0 ? owner.Systems[0] : null);
                if (position.X < system.Left || position.X > system.Left + system.Width || position.Y < 0)
                    system = null;
            }

            if (system == null || system.Staves.Count == 0)
            {
                activeMeasure = null;
                ActiveStaff = null;
                IsSymbolVisible = false;
                return;
            }

            var staff = system.Staves[0];
            for (int i = 1; i < system.Staves.Count; ++i)
            {
                if (position.Y < system.Staves[i].Top)
                {
                    var prevStaff = system.Staves[i - 1];
                    if (system.Staves[i].Top - position.Y < position.Y - prevStaff.Bottom)
                        staff = system.Staves[i];
                }
                else
                    staff = system.Staves[i];
            }

            ActiveStaff = staff;
            activeStaffPosition = (int)Math.Round((staff.Bottom - position.Y) / (staff.SpaceSize / 2));

            Barline barline = null;
            foreach (var bl in system.Barlines)
            {
                if (bl.Position > position.X)
                    break;
                else
                    barline = bl;
            }

            if (barline == null)
            {
                activeMeasure = null;
                IsSymbolVisible = false;
                return;
            }

            activeMeasure = ((Model.Elements.CWNotation.Barline)barline.AdaptedElement).Right;

            if (SymbolType == InsertedSymbolType.Note)
                Position = position.X - ActiveStaff.SpaceSize * 0.5;
            else
                Position = position.X - ActiveStaff.SpaceSize * 0.75;

            VerticalPosition = staff.Bottom - activeStaffPosition * (staff.SpaceSize / 2);
            SetLedgerLines(activeStaffPosition);
            IsSymbolVisible = true;
        }

        /// <summary>
        /// Initializes and shows the inserter.
        /// </summary>
        public void ShowInserter()
        {
            Init();
            isVisible = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the accidental that the inserted note symbol will have.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="AccidentalType"/>
        /// </para>
        /// <para>
        /// The accidental that the inserted note symbol will have.
        /// This value is meaningful only if the <see cref="SymbolType"/>
        /// is <see cref="InsertedSymbolType.Note"/>. Otherwise it is ignored.
        /// </para>
        /// </value>
        public AccidentalType Accidental
        {
            get { return accidental; }
            set
            {
                accidental = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Accidental"));
            }
        }

        /// <summary>
        /// Gets the currently active staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that is currently active (i.e. the inserter would create a symbol in it).
        /// </para>
        /// </value>
        public Staff ActiveStaff
        {
            get { return activeStaff; }
            private set
            {
                activeStaff = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("ActiveStaff"));
                    PropertyChanged(this, new PropertyChangedEventArgs("EffectiveTypeSize"));
                }
            }
        }

        /// <summary>
        /// Gets or sets the number of augmentation dots that the inserted symbol will have.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The number of augmentation dots that the inserted symbol will have.
        /// </para>
        /// </value>
        public byte AugmentationDots
        {
            get { return augmentationDots; }
            set
            {
                augmentationDots = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("AugmentationDots"));
            }
        }

        /// <summary>
        /// Gets or sets the basic duration of the inserted symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The basic duration of the inserted symbol.
        /// </para>
        /// </value>
        public ushort BasicDuration
        {
            get { return basicDuration; }
            set
            {
                basicDuration = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("BasicDuration"));
            }
        }
        
        /// <summary>
        /// Gets the effective type size of the inserter symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The effective type size (in device independent pixels) of the inserter symbol.
        /// </para>
        /// </value>
        public double EffectiveTypeSize
        {
            get { return (ActiveStaff != null ? ActiveStaff.Height : 26.5); }
        }

        /// <summary>
        /// Gets the value indicating whether the inserter symbol is currently visible or not.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the inserter symbol is currently visible or not.
        /// </para>
        /// </value>
        public bool IsSymbolVisible
        {
            get { return isVisible && symbolVisible; }
            set
            {
                symbolVisible = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("IsSymbolVisible"));
            }
        }

        /// <summary>
        /// Gets the collection of ledger lines.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="InserterLedgerLine"/>
        /// </para>
        /// <para>
        /// The collection of ledger lines.
        /// </para>
        /// </value>
        public ObservableCollection<InserterLedgerLine> LedgerLines
        {
            get { return ledgerLines; }
        }

        /// <summary>
        /// Gets the horizontal position of the inserter symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the inserter symbol
        /// from the left edge of the owner section.<br/>
        /// This value can be slightly different from what was set in <see cref="SetPosition"/>,
        /// because it corresponds to the position of the left edge of the inserter symbol,
        /// whilst the mouse pointer is typically held in the center of that symbol.
        /// </para>
        /// </value>
        public double Position
        {
            get { return position; }
            private set
            {
                position = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Position"));
            }
        }

        /// <summary>
        /// Gets or sets the type of the inserted symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="InsertedSymbolType"/>
        /// </para>
        /// <para>
        /// The type of the inserted symbol.
        /// </para>
        /// </value>
        public InsertedSymbolType SymbolType
        {
            get { return symbolType; }
            set
            {
                symbolType = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("SymbolType"));
            }
        }

        /// <summary>
        /// Gets the vertical position of the inserter symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the inserter symbol
        /// from the top of the owner section.<br/>
        /// This position can be slighthly different from what was set in <see cref="SetPosition"/>,
        /// because it corresponds to the computed staff position of the inserter symbol.
        /// </para>
        /// </value>
        public double VerticalPosition
        {
            get { return verticalPosition; }
            private set
            {
                verticalPosition = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("VerticalPosition"));
            }
        }

        /// <summary>
        /// Gets or sets the voice to which the symbol will be inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The voice to which the symbol will be inserted (0 means default staff voice).
        /// </para>
        /// </value>
        public byte Voice
        {
            get { return voice; }
            set
            {
                voice = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Voice"));
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Accidental"/> property.
        /// </summary>
        private AccidentalType accidental;
        /// <summary>
        /// Holds the active measure.
        /// </summary>
        private Model.Elements.MeasureElement activeMeasure;
        /// <summary>
        /// Holds the value of the <see cref="ActiveStaff"/> property.
        /// </summary>
        private Staff activeStaff;
        /// <summary>
        /// Holds the active staff position.
        /// </summary>
        private int activeStaffPosition;
        /// <summary>
        /// Holds the value of the <see cref="AugmentationDots"/> property.
        /// </summary>
        private byte augmentationDots;
        /// <summary>
        /// Holds the value of the <see cref="BasicDuration"/> property.
        /// </summary>
        private ushort basicDuration;
        /// <summary>
        /// Indicates whether the inserter is currently visible.
        /// </summary>
        private bool isVisible;
        /// <summary>
        /// Holds the collection of ledger lines.
        /// </summary>
        private ObservableCollection<InserterLedgerLine> ledgerLines;
        /// <summary>
        /// Holds the pool of ledger lines.
        /// </summary>
        private List<InserterLedgerLine> ledgerPool;
        /// <summary>
        /// Holds the owner of this inserter.
        /// </summary>
        private MusicSectionInterior owner;
        /// <summary>
        /// Holds the value of the <see cref="Position"/> property.
        /// </summary>
        private double position;
        /// <summary>
        /// Holds the value of the <see cref="SymbolType"/> property.
        /// </summary>
        private InsertedSymbolType symbolType;
        /// <summary>
        /// Holds the value of the <see cref="IsSymbolVisible"/> property.
        /// </summary>
        private bool symbolVisible;
        /// <summary>
        /// Holds the auxiliary collection of rectangles  that represent the areas of the systems.
        /// </summary>
        private List<Rect> systemRects;
        /// <summary>
        /// Holds the value of the <see cref="VerticalPosition"/> property.
        /// </summary>
        private double verticalPosition;
        /// <summary>
        /// Holds the value of the <see cref="Voice"/> property.
        /// </summary>
        private byte voice;

        #endregion
    }

    /// <summary>
    /// Describes the type of the inserted symbol.
    /// </summary>
    public enum InsertedSymbolType
    {
        /// <summary>
        /// Bass Clef.
        /// </summary>
        BassClef,
        /// <summary>
        /// Tenore clef.
        /// </summary>
        TenoreClef,
        /// <summary>
        /// Treble Clef.
        /// </summary>
        TrebleClef,
        /// <summary>
        /// Note (Chord).
        /// </summary>
        Note,
        /// <summary>
        /// Rest.
        /// </summary>
        Rest
    }

    /// <summary>
    /// Implements a ledger line for a <see cref="CoreSymbolInserter"/>.
    /// </summary>
    public class InserterLedgerLine : INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="InserterLedgerLine"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CoreSymbolInserter"/>
        /// </para>
        /// <para>
        /// The core symbol inserter that owns this ledger line.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        public InserterLedgerLine(CoreSymbolInserter owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            owner.PropertyChanged += new PropertyChangedEventHandler(owner_PropertyChanged);
            this.owner = owner;
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <inheritdoc/>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the value of an owner's property has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void owner_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Position")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("X1"));
                    PropertyChanged(this, new PropertyChangedEventArgs("X2"));
                }
            }
            else if (e.PropertyName == "ActiveStaff")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("X1"));
                    PropertyChanged(this, new PropertyChangedEventArgs("X2"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Y"));
                }
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the staff position to which this line corresponds.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The staff position to which this line corresponds.
        /// </para>
        /// </value>
        public int StaffPosition
        {
            get { return staffPosition; }
            set
            {
                staffPosition = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("StaffPosition"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Y"));
                }
            }
        }

        /// <summary>
        /// Gets the horizontal position of the line's starting point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the line's starting point
        /// from the left edge of the owner section.
        /// </para>
        /// </value>
        public double X1
        {
            get { return owner.Position - (owner.ActiveStaff != null ? owner.ActiveStaff.SpaceSize * 0.5 : 0); }
        }

        /// <summary>
        /// Gets the horizontal position of the line's ending point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the line's ending point
        /// from the left edge of the owner section.
        /// </para>
        /// </value>
        public double X2
        {
            get { return owner.Position + (owner.ActiveStaff != null ? owner.ActiveStaff.SpaceSize * 1.5 : 0); }
        }

        /// <summary>
        /// Gets the vertical position of the line.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the line
        /// from the top edge of the owner section.
        /// </para>
        /// </value>
        public double Y
        {
            get { return (owner.ActiveStaff != null ? owner.ActiveStaff.Bottom - staffPosition * (owner.ActiveStaff.SpaceSize / 2) : 0); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the owner of this ledger line.
        /// </summary>
        private CoreSymbolInserter owner;
        /// <summary>
        /// Holds the value of the <see cref="StaffPosition"/> property.
        /// </summary>
        private int staffPosition;

        #endregion
    }
}
